<?php
namespace App\Components\Admin;

use App\Components\Redis;
use ReflectionClass;
use ReflectionMethod;

class Node
{
    // 控制器命名空间前缀
    const CTL_NAMESPACE_PREFIX = 'App\Controllers\Admin';

    const METHOD_NODE_AUTH_LABEL = '@auth';

    const METHOD_NODE_MENU_LABEL = '@menu';

    public static function availableNode(bool $isRefresh = false): array
    {
        $cacheKey = 'COM:ADMIN:NODE:AVAILABLENODE';
        $cacheRs = Redis::get($cacheKey);
        if ($cacheRs !== false && $cacheRs !== null && $isRefresh === false) {
            return json_decode($cacheRs, true);
        }

        $classRs = self::getAllFilesClass();
        $return = [];
        foreach ($classRs as $v1) {
            $class = new \ReflectionClass($v1);
            // 解析类注释，作为节点父名称
            $docComment = $class->getDocComment();
            if ($docComment === false) {
                continue;
            }
            $classDesc = self::parseDocComment($docComment);
            if (!isset($classDesc[0])) {
                $classDesc = [$v1];
            }
            $classDesc = implode('， ', $classDesc);

            $node = ['name' => $classDesc, 'node' => $class->getName(), 'children' => []];

            $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
            foreach ($methods as $v2) {
                // 检测方法参数入参，是否符合请求、响应
                $parameters = $v2->getParameters();
                if (!isset($parameters[1])) {
                    continue;
                }
                $parametersClass = $parameters[0]->getClass();
                if (!($parametersClass instanceof ReflectionClass)) {
                    continue;
                }
                if (strpos($parametersClass->getName(), 'ServerRequestInterface') === false) {
                    continue;
                }
                $parametersClass = $parameters[1]->getClass();
                if (!($parametersClass instanceof ReflectionClass)) {
                    continue;
                }
                if (strpos($parametersClass->getName(), 'ResponseInterface') === false) {
                    continue;
                }

                // 解析方法注释，判断是否符合要求
                $docComment = $v2->getDocComment();
                if ($docComment === false) {
                    continue;
                }
                $methodDesc = self::parseDocComment($docComment);
                if (!in_array(self::METHOD_NODE_AUTH_LABEL, $methodDesc)) {
                    continue;
                }
                $method = ['name' => '', 'node' => $v2->getName(), 'menu' => false];
                if (in_array(self::METHOD_NODE_MENU_LABEL, $methodDesc)) {
                    $method['menu'] = true;
                }
                foreach ($methodDesc as $k3 => $v3) {
                    if (strpos($v3, '@') === 0) {
                        unset($methodDesc[$k3]);
                    }
                }
                $method['name'] = implode('， ', $methodDesc);
                $node['children'][] = $method;
            }

            if (isset($node['children'][0])) {
                $return[] = $node;
            }
        }

        if ($_ENV['app_debug'] === true) {
            Redis::setex($cacheKey, Redis::tinyTime(), json_encode($return));
        } else {
            Redis::setex($cacheKey, Redis::mediumTime(), json_encode($return));
        }
        return $return;
    }

    private static function getAllFilesClass(string $path = ''): array
    {
        if ($path == '') {
            $path = ROOT_PATH . lcfirst(self::CTL_NAMESPACE_PREFIX);
            $path = str_replace('\\', '/', $path);
        }
        $fileRs = scandir($path);
        $return = [];
        foreach ($fileRs as $v) {
            if ($v == '.' || $v == '..') {
                continue;
            }
            $v = $path . DIRECTORY_SEPARATOR . $v;
            if (is_dir($v)) {
                $return = array_merge($return, self::getAllFilesClass($v));
            } else {
                $extension = strtolower(pathinfo($v, PATHINFO_EXTENSION));
                if ($extension !== 'php') {
                    continue;
                }

                $class = str_replace(ROOT_PATH, '', $v);
                $class = substr($class, 0, -4);
                $class = ucfirst($class);
                $return[] = str_replace('/', '\\', $class);
            }
        }
        return $return;
    }

    private static function parseDocComment(string $comment): array
    {
        preg_match('#^/\*\*(.*)\*/#s', $comment, $describe);
        if (!isset($describe[1])) {
            return [];
        }
        preg_match_all('#^\s*\*(.*)#m', trim($describe[1]), $lines);
        if (!isset($lines[1])) {
            return [];
        }
        return array_values(array_filter(array_map(function ($v) {
            return trim($v);
        }, $lines[1])));
    }
}
